結論先說:evaluation 通過不代表沒有問題,只代表 candidate 沒有重蹈已知的覆轍;release gate 要輸出的是條件與證據,不是一個綠色勾勾。這一篇(下)把 DIY 走完,接回本文最初的結論。
承接上文:上一篇(中)建好了 project、trace 邊界、WorkflowContext 與 dataset 骨架(Step 1 到 Step 8)。這一篇接著跑 deterministic evaluator 的比較、LLM-as-judge 的校準防線、feedback triage、investigation runbook,直到把整條 trace → dataset → evaluation → release gate 的學習迴圈收斂成一份可交接的 evidence 包。
平均分數很容易掩蓋高風險錯誤。先將可審計的規則拆開,例如 citation、預期 task status、必要事實與禁止的 tool action。
def source_is_present(outputs: dict, reference_outputs: dict) -> dict:
actual = set(outputs.get("citations", []))
required = set(reference_outputs.get("required_sources", []))
passed = required.issubset(actual)
return {
"key": "required_source_present",
"score": int(passed),
"comment": f"required={sorted(required)}, actual={sorted(actual)}",
}
def task_status_matches(outputs: dict, reference_outputs: dict) -> dict:
expected = reference_outputs["expected_task_status"]
actual = outputs.get("task_status")
return {
"key": "task_status_matches",
"score": int(actual == expected),
"comment": f"expected={expected}, actual={actual}",
}
def required_fact_present(outputs: dict, reference_outputs: dict) -> dict:
expected = reference_outputs.get("required_fact", "")
answer = outputs.get("answer", "")
passed = not expected or expected in answer
return {
"key": "required_fact_present",
"score": int(passed),
"comment": "literal Lab check; production needs a reviewed semantic rule",
}
把服務包成 evaluation target 時,輸出要和 dataset reference contract 對齊。不要讓 evaluator 猜 response 裡哪個欄位叫 answer。
def make_context(
*,
request_id: str,
prompt_version: str,
model_route: str,
) -> WorkflowContext:
return WorkflowContext(
request_id=request_id,
task_id="task-evaluation",
workflow_name="policy-qa",
workflow_version="2026-09-22",
service_version="answer-api-0.9.0",
deployment_id="lab-local",
prompt_version=prompt_version,
policy_version="policy-guard-v1",
retrieval_index_version="policy-v3",
model_route=model_route,
)
def baseline_target(inputs: dict) -> dict:
result = answer_workflow(
inputs["question"],
make_context(
request_id="eval-baseline",
prompt_version="policy-v1",
model_route="baseline",
),
)
return result
def candidate_target(inputs: dict) -> dict:
result = answer_workflow(
inputs["question"],
make_context(
request_id="eval-candidate",
prompt_version="policy-v2",
model_route="candidate",
),
)
return result
make_context() 應在你的服務中集中處理版本與 deployment 資訊;不要讓每個 evaluator 都各自拼出一份不同 metadata。這類不一致會讓 incident 時的比較變成猜謎。
官方 SDK 的 Client.evaluate() 可針對 dataset 執行 target 與 evaluators。先以低 concurrency 在 Lab 觀察成本與 dependency 行為;提高 concurrency 前,要確認 provider rate limit、queue、cost budget 與 evaluation timeout。
evaluators = [
source_is_present,
task_status_matches,
required_fact_present,
]
baseline_results = client.evaluate(
baseline_target,
data="policy-task-dataset-v1",
evaluators=evaluators,
experiment_prefix="policy-baseline-v1",
description="Baseline prompt and routing for sanitized Lab cases.",
max_concurrency=1,
)
candidate_results = client.evaluate(
candidate_target,
data="policy-task-dataset-v1",
evaluators=evaluators,
experiment_prefix="policy-candidate-v2",
description="Candidate prompt and routing for sanitized Lab cases.",
max_concurrency=1,
)
這兩個 result 物件的 UI 與 SDK 查詢方式會隨 LangSmith 版本演進;release gate 不應依賴手動閱讀一張表。讓 CI 或 review script 明確輸出每個 required case 的 pass/fail,以及要採取的 decision。
case: policy_approval_required
required_source_present: pass
task_status_matches: pass
required_fact_present: fail
decision: blocked
case: unknown_supplier_discount
required_source_present: pass
task_status_matches: pass
required_fact_present: pass
decision: allowed
第一個案例即使整體平均 0.95,也要 blocked。因為它漏的是經確認的必要條件;平均分數不能替風險分類做決定。
這一步在驗證什麼:這是整篇文章④⑤兩段最核心的主張,落到程式碼的樣子——「用平均分數決定要不要放行」是一個看起來合理、實際上會漏接高風險錯誤的做法。三個 evaluator 函式刻意寫成各自獨立、各自回傳 0 或 1,而不是合併成一個加權平均分數,正是為了讓 policy_approval_required 這個案例即使整體分數 0.95 依然被攔下來。如果讀者把這三個函式改寫成「三項加總除以三」,這個案例反而會因為兩項滿分被平均分數蓋過去,這正是文章反覆強調「平均分數不能替風險分類做決定」想要避免的結果。
跑的時候預期看到什麼:client.evaluate() 執行完,baseline 與 candidate 各自會產生一個 experiment,每個 dataset example 對應到三個 evaluator 的分數。讀者應該會看到 baseline 和 candidate 在某些案例上表現一致、在少數案例上出現差異——如果兩者完全一樣,通常代表 baseline_target 和 candidate_target 沒有真的接到不同的 prompt 版本或 model route,值得回頭檢查 make_context() 的參數是不是被複製貼上時忘了改。
容易踩的坑:max_concurrency 這個參數在 Lab 環境刻意設為 1,是因為真實 provider 呼叫有 rate limit,跑十幾筆 dataset case 同時發送並不會比較快,反而容易觸發 429。另一個常見的坑是 dataset 裡的 reference_outputs schema 和 evaluator 函式期待的欄位名稱對不上——例如 dataset 存的是 required_source(單數),evaluator 卻讀 required_sources(複數),這種拼字誤差不會讓程式報錯,只會讓每一筆 case 都安靜地評分為 0,錯誤地以為 candidate 表現很差。
有些品質問題不能用字串包含判斷,例如回答有沒有扭曲政策、摘要是否遺漏限制、引用是否真的支持結論。這時才考慮 LLM-as-a-judge。
它是 evaluator,不是真相產生器。為 judge 設定自己的 contract:
judge input
- sanitized question
- candidate answer
- approved reference facts
- allowed source excerpt or source ID
judge output
- pass / fail / uncertain
- failure category
- short rationale
- evaluator prompt version
- evaluator model route
在把 judge score 當 release evidence 前,至少做三件事:
fail、uncertain、高風險類別與分數臨界值附近的案例做人工複核。不要把 judge 的 explanation 直接回傳給使用者,也不要讓 judge 決定 tool 是否有寫入權限。安全 permission 應由可審計的 policy engine 或人工批准處理;judge 最多提供一條需要調查的訊號。
第一步驟提到的「校準」不是空話,它應該產出一份具體的報告,讓團隊看見 judge 到底可不可信:
judge calibration report — policy-judge-v1
Gold set 規模:40 筆人工審過的案例
judge 與人工判定一致:34 筆(85%)
disagreement 分布:
judge=pass,人工=fail → 4 筆(judge 過於寬鬆)
judge=fail,人工=pass → 1 筆(judge 過於嚴格)
judge=uncertain → 1 筆(正確標記為需人工複核)
結論:judge 在「引用是否支持結論」這類語意判斷上偏寬鬆,
高風險類別(policy_exception、safety-adjacent)
應強制人工複核,不論 judge 分數為何。
這一步在驗證什麼:這份校準報告把前面提到的 Claude「察覺自己在被評估」現象,轉換成一個可操作的防線——不是要求團隊去偵測模型有沒有在自我調整,而是持續拿一小批人工審過的 Gold set 去測 judge 本身準不準。如果 disagreement 的分布長期偏向某一方(例如 judge 系統性地偏寬鬆),這就是明確訊號:不能再把這個 judge 的分數直接當成 release evidence,得先修正 judge 的 prompt 或提高人工複核比例。
容易踩的坑:Gold set 的規模如果太小(例如只有 5、6 筆),disagreement 的統計就沒有意義——4 筆裡錯 1 筆和 40 筆裡錯 10 筆,錯誤率看起來一樣,但後者的結論可信得多。另一個常見錯誤,是把「人工複核」設計成只複核 judge 判定為 fail 的案例,卻忽略了 judge 判定 pass 但其實應該 fail 的偽陰性——這類案例才是真正危險的地方,因為它們會被 release gate 直接放行,而不會被任何人注意到。
使用者按下倒讚,不等於系統已經被證明答錯;它是 investigation ticket。feedback 至少應有 key、score、comment、trace ID 和分類結果。
LangSmith Python SDK 可以以 create_feedback() 將 feedback 關聯 root trace 或 child run。傳入 trace_id 時,SDK 能以背景方式處理 feedback ingestion,避免把 UI 回報路徑硬塞進低延遲 request 的同步等待。
from langsmith import Client
client = Client()
def record_user_feedback(trace_id: str, message: str) -> None:
client.create_feedback(
key="user_feedback",
score=0,
trace_id=trace_id,
comment=message,
)
若已查明錯誤落在 retrieval,而不是整條 workflow,可將 feedback 掛在對應 child run。同一個 trace 若有多筆回報,保留時間、來源與處理狀態,別用最後一個 comment 覆蓋前面的證據。
new 使用者剛回報,尚未重現
triaged 已確認 trace、版本與 request 範圍
confirmed 已確認為產品或資料問題
not_reproduced 目前證據不足,保留原因與查詢時間窗
converted 已遮罩並轉成 dataset case
一個合理的 triage 記錄看起來像這樣:
{
"trace_id": "trace-...",
"request_id": "req-20260922-41",
"feedback_state": "confirmed",
"failure_class": "missing_critical_condition",
"prompt_version": "policy-v2",
"retrieval_index_version": "policy-v3",
"next_action": "create_sanitized_regression_case",
"owner": "ai-service-oncall"
}
資料中的 owner 應是 on-call alias 或 team role,而非私人聯絡資訊。若 comment 含敏感資料,先在服務端做 redaction;不要期望後續人工看見後才把資料「忘掉」。
當 quality alert、feedback 或 Day 30 chaos experiment 出現異常,依序查,而不是先猜 prompt 壞了。
| 次序 | 問題 | 優先證據 | 可能處置 |
|---|---|---|---|
| 1 | 是否為同一 deployment 後開始? | release manifest、deployment_id、metrics window |
停止 rollout、比較 canary |
| 2 | transport 是否成功? | HTTP status、error rate、dependency span | 依 Day 18/29 處理 technical failure |
| 3 | retrieval 是否有資料且版本正確? | result_count、index_version、retriever run |
重建或回退 index,或降級 |
| 4 | 模型 route 是否改變? | model_route、fallback flag、latency |
檢查 provider 或 routing policy |
| 5 | tool 是否被允許與正確執行? | policy decision、tool run、audit event | 停止 action、人工審核 |
| 6 | validator 是否放過不應放行的回覆? | quality/safety/task status | 修正規則並建立 regression case |
| 7 | 已知 failure 是否被 dataset 擋住? | dataset tag、experiment、evaluator detail | blocked、補 evaluator 或案例 |
用一個具體案例走一次。使用者問「公司每週能遠端三天,對嗎?」系統回「可以」,HTTP 200,卻漏了政策中的主管核准與天數限制。
request_id → 找到 root trace
root trace → prompt_version=policy-v2,index_version=policy-v3
retrieve run → 找到 Remote Work Policy,非 empty retrieval
model run → provider 正常、沒有 fallback
validator run → quality_status=pass,但沒有檢查必要條件
判定 → validator coverage gap,不是 provider outage
後續 → 建立 missing_critical_condition dataset case,candidate 必須 blocked
這個結論靠 evidence 得出,不是因為「模型看起來答得很自信」。同樣地,若 retriever 根本沒拿到文件,修 validator 只能讓系統更常拒絕,不能修好資料供應。
換一個根因不同的案例,會走到表格裡不同的分支。使用者問「上季的供應商合約條款是什麼?」系統一樣回 HTTP 200,回答卻是一段語氣自信、內容卻完全編造的合約條款。
request_id → 找到 root trace
root trace → prompt_version=policy-v2,index_version=policy-v3
retrieve run → result_count=0,empty retrieval(知識庫裡根本沒有這份合約)
model run → provider 正常、沒有 fallback
validator run → quality_status=insufficient_context 本應觸發,
但實際輸出卻是 quality_status=pass
判定 → validator 沒有正確處理 empty retrieval 的情況,
讓模型在沒有依據時仍自信生成答案
後續 → 這不是 dataset 覆蓋不足的問題,是 validate_answer() 的
if not documents 分支本身有 bug(見 Step 6 的範例實作),
必須先修程式碼,再補一筆 dataset case 防止回歸
把這兩個案例放在一起看,價值在於它們走到表格裡完全不同的分支——第一個案例的根因在「規則沒寫全」(validator 沒有檢查某個必要條件),第二個案例的根因在「規則本身有 bug」(empty retrieval 該觸發的分支沒有被觸發)。這兩種根因的修法不一樣:前者是補規則、寫 dataset case;後者是先修程式碼本身的邏輯錯誤,dataset case 只是用來防止同樣的 bug 以後又悄悄回來。investigation runbook 表格的價值,正是逼著使用者在下結論前,先把這兩種可能性分開檢查,而不是看到「答案錯了」就直接跳去改 prompt。
release gate 應輸出條件,而非只輸出一個綠色勾勾。以下是可供 Lab 改寫的 decision spec。
release_candidate:
candidate_id: answer-api-0.9.0-rc2
prompt_version: policy-v2
dataset_tag: release-candidate-2026-09-22
required_checks:
- required_source_present
- task_status_matches
- required_fact_present
- tool_permission_denied_when_required
block_when:
- any_required_case_failed
- safety_case_failed
- dataset_version_not_pinned
- evaluator_version_missing
human_review_when:
- llm_judge_uncertain
- new_failure_class
- evaluation_coverage_changed
evidence:
- experiment_link
- release_manifest
- evaluator_versions
- approval_record
這份檔案不是 LangSmith 的內建設定格式,而是團隊自己的 release contract 範例。它故意把「資料版本未固定」視為阻擋條件:不能確定比較的是哪份題目,就不能宣稱 candidate 通過。
decision 本身也應有可追溯紀錄。
{
"candidate_id": "answer-api-0.9.0-rc2",
"dataset_tag": "release-candidate-2026-09-22",
"experiment": "policy-candidate-v2",
"decision": "human_review_required",
"reasons": ["llm_judge_uncertain on policy_exception_case"],
"approver_role": "ai-service-owner",
"recorded_at": "2026-09-22T10:30:00Z"
}
若 decision 是 blocked,不要把候選版本偷偷調低 traffic 當成「有推出」。應明確回到 owner、修正、再評估;若是 human_review_required,指派能做決定的角色,避免 approval 變成無限期待辦。
這一步在驗證什麼:這一步把⑦段落的核心主張——「release gate 輸出的是條件,不是一個綠色勾勾」——寫成具體的 YAML 結構。它驗證的重點在於 block_when 裡刻意加入了 dataset_version_not_pinned 和 evaluator_version_missing 這兩條,這兩條不是在測 candidate 的品質,而是在測「這次比較本身有沒有資格被信任」。讀者實際跑一次就會發現,如果沒有事先在 Step 8 固定 dataset tag,這裡的 release gate 邏輯應該要主動擋下來,而不是安靜地拿最新資料跑完評估、給出一個看似正常的分數。
預期看到什麼、容易踩的坑:這份 YAML 和上面的 JSON decision 記錄不是 LangSmith 提供的內建功能,讀者需要自己寫一小段程式把 client.evaluate() 的結果轉換成這個結構。常見的偷懶做法是把 block_when 的條件寫死在程式碼裡、卻沒有寫測試驗證它,結果過了幾個月團隊新增了一個 safety_case_failed 的判斷邏輯,卻忘了同步更新這份 YAML 的說明文件,兩邊逐漸不同步。比較穩妥的做法,是讓 release decision 的產生程式碼本身就讀取這份 YAML 當作設定來源,而不是讓 YAML 淪為一份寫給人看、程式碼卻沒有真的照著做的文件。
LangSmith trace 可以讓你從一筆失敗回到 workflow;它不能單獨告訴你 error budget 是否正在燃燒。把兩條證據鏈在 service 邊界用相同 ID 關聯。
HTTP handler
├─ structured log: request_id, trace_id, task_status, deployment_id
├─ OTel span: request_id, workflow_version, dependency outcome
├─ LangSmith trace: prompt/retrieval/model/tool/validator evidence
└─ Prometheus metric: route, technical_status, task_status, degraded
metric 設計要保守。例如:
ai_requests_total{route="ask",technical_status="success",task_status="completed"}
ai_requests_total{route="ask",technical_status="success",task_status="degraded"}
ai_requests_total{route="ask",technical_status="error",task_status="failed"}
它能支援 Day 14 的 SLI 分母與分子討論;但 request_id、trace_id、prompt 全文、dataset example ID 都不該進去。
alert 觸發後的調查節奏可以是:
Grafana:quality-related outcome 或 dependency error 是否異常?
↓
Loki:用時間窗、route、deployment_id 找到相關 request_id
↓
OpenTelemetry:確認跨服務與 dependency path
↓
LangSmith:檢查該 request 的 workflow child run、版本與 feedback
↓
Dataset/evaluation:是否已有 regression case?candidate 是否重現?
這是查詢順序,不是要求每次都開四個工具。若 Prometheus 顯示 provider timeout,先從服務與 dependency telemetry 開始;若只有某類 feedback 上升,才從 dataset 與 evaluation coverage 往回找。工具各有責任,少繞路才是成熟的 on-call。
這一步在驗證什麼:這是全篇唯一一個把 Day 2、Day 14 建立的 Prometheus/Loki/OTel 觀測堆疊和本篇的 LangSmith 明確接起來的 Step,驗證的是①段落「三層各司其職、不互相取代」的主張要能落地,前提是四層系統必須共享同一組 ID(request_id/trace_id)。預期看到什麼:在 Loki 用 request_id 查到一筆 log 後,同一個值應該能在 LangSmith 的 trace 搜尋欄位直接貼上去找到對應的 root run,中間不需要任何轉換或對照表。容易踩的坑:最常見的失誤是 metric label 裡不小心放進高基數欄位(例如把 request_id 當成 label),這會讓 Prometheus 的時間序列數量爆炸;另一個常見狀況,是四層系統分別由不同人建置,request_id 在某一層被重新產生而非全程傳遞,導致查詢鏈在某個環節突然斷掉,Step 2 提到的「沒有正確傳遞 ID」在這裡會以跨系統關聯失敗的方式重新出現。
| 現象 | 容易犯的錯 | 較好的處理 |
|---|---|---|
| trace 內出現完整客戶文件 | 以為之後手動刪掉就好 | 停止擴散、依資料流程處理,並在 ingest 前遮罩 |
| evaluator 平均分提高 | 直接放行 candidate | 先檢查 required case、safety case 和 coverage 是否變更 |
| feedback 都掛 root trace | 認為已能定位 | triage 後將確認的問題指到 retrieval、tool 或 validator child run |
| 只有 completed 被視為成功 | 把誠實拒答當 outage | 依產品 SLI 明確定義 completed、degraded、failed 的計算 |
| empty retrieval 後仍生成自信答案 | 只調高模型 temperature | 先修 retrieval-to-validator contract 與降級 policy |
| 每次 dataset 都取最新版本 | CI 偶爾通過、偶爾失敗 | pin dataset tag,記錄 freeze time 與修改理由 |
| 將 LLM judge 當 final approver | 讓不穩定 evaluator 決定高風險 action | 保留 rule-based checks、人工複核與 policy boundary |
| 同時從多個 SDK 雙寫同一 trace | UI 出現重複 run,成本難算 | 指定 primary tracing path,先在 staging 驗證 context propagation |
| dataset case 只寫「應該做什麼」 | evaluator 無法判斷「不該做什麼」 | 同時寫 forbidden behavior,例如禁止未授權 tool 呼叫 |
| release decision 只留在聊天訊息裡 | 下次同類問題重新討論一次 | 落地成 Step 13 的 JSON 紀錄,附 dataset tag 與 approver |
| 把 LangSmith trace 當成唯一備份 | 帳號權限異動或 retention 到期後證據消失 | evidence 包(Step 16)另存一份低敏感度快照 |
這些問題都不是「LangSmith 壞掉」。它們多半是資料治理、contract 或 release process 沒有被寫清楚。工具會忠實記錄你送給它的東西,包括不該送的東西。
這張表也可以反過來讀:每一列「較好的處理」欄位,幾乎都對應到前面①到⑦某個段落已經講過的原則。這不是巧合——常見的失敗模式,多半就是團隊在壓力下略過了某個已知的最佳實踐,而不是遇到了什麼全新的未知問題。把這張表釘在團隊內部的 wiki 或 onboarding 文件裡,比事後每次都重新討論一次「這次到底該怎麼修」更有效率。
讀者完成自己的實作後,可整理下列資料給 reviewer。不要附 API key、完整內部政策或未遮罩 trace。
1. trace 結構截圖或低敏感度匯出:正常與 degraded 各一筆
2. trace data contract:欄位、用途、retention owner
3. dataset case:來源、遮罩方式、expected behavior、risk level
4. evaluator 定義:版本、規則與已知盲點
5. baseline/candidate experiment:dataset tag、時間、結果、差異
6. release decision:allowed / human_review_required / blocked 與理由
7. 關聯證據:request_id、deployment_id、metrics/logs/OTel 查詢位置
這份 evidence 包要能回答一個很實際的問題:
你說 candidate 比較好。是哪一筆資料、哪一套規則、哪一個版本,以及誰決定它足以推出?
答不出來時,不代表產品一定有問題;代表 release decision 還沒有足夠證據。把證據補齊,比把 dashboard 截圖貼得更漂亮有用。
這一步在驗證什麼:Step 16 不是額外的收尾儀式,而是驗證整個 learning loop 有沒有真的「可交接」。前面十五個 Step 各自證明了某個機制能動,這一步證明的是這些機制產生的證據能不能脫離作者本人、被另一個團隊成員在不追問的情況下看懂。預期看到什麼:把這七項證據交給一個完全沒參與這次 Lab 的同事,對方應該能在不用再問你任何問題的情況下,自己判斷 release decision 是否合理。容易踩的坑:最常出現的缺口是第 4 項「evaluator 定義」,團隊往往只附上 evaluator 的程式碼,卻沒寫「已知盲點」——也就是這個 evaluator 檢查不到什麼。少了這一項,reviewer 很容易誤以為「evaluator 通過」等於「沒有任何問題」,這正好落回⑤段落一開始就在提醒的:evaluation 只能告訴你它有檢查的部分沒有變差,檢查不到的地方,本來就不在它的覆蓋範圍內。
跑完整套 Lab 後,回頭用這張表核對一次,比較容易發現有沒有漏掉某個環節——尤其是「有 trace,但 dataset 和 evaluation 沒接上」這種只做一半的狀況,光看 LangSmith UI 上有畫面,並不代表整條 learning loop 真的閉環了。
| Step | 對應段落 | 驗證的論點 | 若失敗,通常代表 |
|---|---|---|---|
| 1 | ① | project 能正確分隔環境 | 環境變數未生效或 API key 有誤 |
| 2 | ①② | workflow 邊界對準工程責任 | function boundary 切得太粗,child run 缺失 |
| 3-4 | ④⑤ | 已知失敗能被寫成規則 | evaluator 邏輯還沒想清楚就急著寫程式 |
| 5 | ② | trace data contract 先想清楚欄位邊界 | 收集了不該收集的敏感欄位 |
| 6-7 | ②③ | root/child run 對應真實 workflow,task_status 可區分 | validator 邏輯藏在其他函式裡 |
| 8 | ④ | dataset 是可版本化、可審查的資產 | dataset 案例沒有來源與遮罩紀錄 |
| 9 | ⑤ | deterministic evaluator 不被平均分數蓋過 | evaluator 被寫成加權平均,漏接高風險案例 |
| 10 | ⑤ | LLM-as-judge 有校準與人工複核防線 | judge 分數被直接當成唯一證據 |
| 11 | ⑥ | feedback 先是調查入口,不是自動事實 | 倒讚被直接當成 regression 案例 |
| 12 | ⑦ | investigation 有固定查詢順序 | 每次故障都憑經驗猜、沒有 runbook 可依循 |
| 13 | ⑦ | release gate 輸出條件而非綠燈 | dataset 版本未固定卻仍宣稱通過 |
| 14 | ①⑦ | AI trace 與既有 SRE 訊號用共同 ID 關聯 | metrics 異常後找不到對應的 LangSmith run |
| 15-16 | 全篇 | 常見錯誤有具體修法、結果可被覆核 | 團隊各自為政,release 決策無法重現 |
這張表本身也可以直接拿來做 Step 16 evidence 包的附件——它讓 reviewer 一眼看出「這次的 candidate 評估,究竟走完了整條迴圈的哪幾段」,而不必逐字重讀十六個 Step 的說明。
task_status、quality_status 可區分。/ask 的 root run 可連回 request_id、task_id 與 deployment version。LangSmith 最有價值的地方不是多一個 trace 畫面。它讓 AI failure 可以回到版本、資料集與評估,再由 evidence 決定變更能否推出去。SRE 的主線沒有變:先定義承諾,量測使用者結果,留下能重現的證據,才談自動化。
把整篇文章的七個段落攤開來看,其實是在回答同一個問題的七個切面:當 AI workflow 出錯時,系統能不能證明「這次不會再錯」,而不是只能證明「這次修好了」。 這兩者的差別,決定了團隊是不斷救火,還是真的在累積可靠性。
只能證明「這次修好了」
↓ 修一個 prompt,人工測幾個問題,看起來正常,上線
↓ 下一次遇到類似情境,重新從頭排查
↓ 團隊的可靠性知識停留在個人記憶裡,離職就丟失
能證明「這次不會再錯」
↓ 修一個 prompt,先寫成 regression case
↓ baseline 與 candidate 都跑過同一批已知失敗
↓ release decision 有據可查,下次同類問題直接查 dataset
↓ 團隊的可靠性知識累積在 dataset、evaluator 與 evidence 包裡
Day 1 到 Day 30 建立的 metrics、logs、trace、SLO、error budget、chaos experiment,回答的是「系統現在健不健康」;Day 31 在這之上多接了一層,回答「這次變更,有沒有讓系統比昨天更可靠」。兩層合在一起,才是完整的 AI Reliability 迴圈——沒有底層的 observability,evaluation 無從得知 production 真正發生了什麼;沒有頂層的 evaluation,observability 只能不斷發現同一類問題,卻沒有機制阻止它重複發生。
@traceable 標註 Python 程式
這個系列從一條 LLM Trace 開始,最後也回到 Trace。差別是,Day 1 的 Trace 只是讓我們看見 AI workflow;Day 31 的 Trace 已經成為一條能連接 SLI/SLO、Error Budget、Metrics、Logs、OpenTelemetry、Dataset、Evaluation、Safety、Task Success 與 Release Decision 的可靠性證據鏈。
Production AI 的核心挑戰,是系統能不能在變更、故障、流量、成本與安全限制下,持續為使用者提供可信、可觀測、可評估、可恢復且可演進的結果。
LangSmith 可以放進這條 AI Reliability Learning Loop;系統能否維持可靠,仍取決於觀測、評估、故障學習與工程決策是否連得起來。
這篇是 Learning SRE for the AI Era 系列的一部分:從 SRE Lab 到 Production AI Reliability。
Build → Trace → Break → Measure → Evaluate → Recover → Improve.